﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Common.Utilities;
using Microsoft.CSharp;

namespace Workflows.Components.Rules
{
    /// <summary>
    /// create evaluator in memory using code dom
    /// </summary>
    /// <param name="obj"></param>
    public delegate void ObjectUpdatedAfterEvaluation(object obj);

    /// <summary>
    /// a thread safe class that can be used in pool
    /// </summary>
    public class CodeEvaluator : IEvaluator 
    {
        public event ObjectUpdatedAfterEvaluation OnObjectUpdated;

        #region members
        private object _Evaluable;
        /// <summary></summary>
        public object Evaluable
        {
            get
            {
                return this._Evaluable;
            }
            set
            {
                this._Evaluable = value;
            }
        }

        private string _CodeBlock;
        /// <summary></summary>
        public string CodeBlock
        {
            get
            {
                return this._CodeBlock;
            }
            set
            {
                this._CodeBlock = value;
            }
        }
        private readonly string _ObjVariableNameInCode;
        #endregion

        /// <summary></summary>
        public CodeEvaluator(object evaluable, string codeBlock)
        {
            this._Evaluable = evaluable;
            this._CodeBlock = codeBlock;

            this._ObjVariableNameInCode = "MyClass_" + DateTime.Now.Ticks.ToString();
            if(this._Evaluable !=null)
            {
                this._ObjVariableNameInCode = this._Evaluable.GetType().ToString().Replace(".", "_");
            }
        }

        /// <summary>
        /// the code block do not contain any method signature
        /// it just returns a boolean value indicating whether it's successfully executed
        /// </summary>
        /// <returns></returns>
        public bool Evaluate()
        {
            string className = this._ObjVariableNameInCode + "_" + this._CodeBlock.GetHashCode().ToString();
            CompilerParameters compilerParms = new CompilerParameters();
            compilerParms.ReferencedAssemblies.Add("System.dll");
            compilerParms.ReferencedAssemblies.Add("System.Xml.dll");
            compilerParms.ReferencedAssemblies.Add("System.Data.dll");
            compilerParms.GenerateInMemory = true;

            StringBuilder block = new StringBuilder();
            block.Append("using System;\n");
            block.Append("using System.Collections.Generic;\n");
            block.Append("using System.ComponentModel;\n");
            block.Append("using System.IO;\n");
            block.Append("using System.Text;\n");
            block.Append("using System.Data;\n");
            // block.Append("using System.Drawing;\n");
            block.Append("using System.Xml;\n\n");
            block.Append("using System.Text.RegularExpressions;\n");
            //block.Append("using System.Windows.Forms;\n");

            block.Append("namespace MyNamespace {\n");
            block.Append("public class " + className + " {\n");

            // property
            if (this._Evaluable != null)
            {
                Type fieldType = this._Evaluable.GetType();
                // field declaration 
                block.Append("private " + fieldType.ToString() + " _" + this._ObjVariableNameInCode + ";\n");
                // get method
                block.Append("public " + fieldType.ToString() + " Get_" + this._ObjVariableNameInCode + "(){\n");
                block.Append("return _" + this._ObjVariableNameInCode + ";\n");
                block.Append("}\n");
                // set method
                block.Append("public void Set_" + this._ObjVariableNameInCode + "(" + fieldType.ToString() +
                             " value){\n");
                block.Append("this._" + this._ObjVariableNameInCode + "=value;\n");
                block.Append("}\n");
            }

            // method
            block.Append("public void Evaluate(){\n");
            block.Append(this._CodeBlock);
            block.Append("} \n");
            block.Append("} \n");
            block.Append("} \n");

            CSharpCodeProvider codeProvider = new CSharpCodeProvider();
            CompilerResults compileResult = codeProvider.CompileAssemblyFromSource(compilerParms, block.ToString());
            if (compileResult.Errors.HasErrors)
            {
                throw new Exception(compileResult.Errors[0].ErrorNumber + ": " + compileResult.Errors[0].ErrorText);
            }
            Assembly localAssem = compileResult.CompiledAssembly;
            object instance = localAssem.CreateInstance("MyNamespace." + className);

            bool succeed = false;
            try
            {
                if (this._Evaluable!=null)
                {
                    instance.GetType().InvokeMember("Set_" + this._ObjVariableNameInCode, BindingFlags.InvokeMethod,
                                                    null, instance,
                                                    new object[] {this._Evaluable});
                }

                instance.GetType().InvokeMember("Evaluate", BindingFlags.InvokeMethod, null, instance, null);
                succeed = true;

                if (this._Evaluable != null)
                {
                    object evaluatedParameter =
                            instance.GetType().InvokeMember("Get_" + this._ObjVariableNameInCode, BindingFlags.InvokeMethod, null, instance,
                                                            null);
                    this._Evaluable = evaluatedParameter;

                    if(this.OnObjectUpdated !=null)
                    {
                        this.OnObjectUpdated(this._Evaluable);
                    }
                }
            }
            catch (Exception ex)
            {
                ExceptionHandler.HandlesException(ex);
                succeed = false;
            }
            return succeed;
        }
    }
}
